/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.query.binding;

import java.util.ArrayList;
import java.util.List;

import edu.ucsd.db.bassarisk.query.binding.Bindings.AggregatedValueBinding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.Binding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.BindingProduct;
import edu.ucsd.db.bassarisk.query.binding.Bindings.BindingUnion;
import edu.ucsd.db.bassarisk.query.binding.Bindings.MapFunctionValueBinding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.PositionAttributeBinding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.PositionBinding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.RunAttributeBinding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.RunBinding;
import edu.ucsd.db.bassarisk.schema.AttributeClass;
import edu.ucsd.db.bassarisk.schema.DataSequenceClass;
import edu.ucsd.db.bassarisk.schema.RunClass;
import edu.ucsd.db.bassarisk.treepattern.EdgeFunction;

public class BindingFactory {
  private final BindingSet bindings;

  public BindingFactory(final BindingSet bindings) {
    this.bindings = bindings;
  }

  public AggregatedValueBinding
      createAggregatedValueBinding(final Binding aggregationContext, EdgeFunction f) {
    AggregatedValueBinding newBinding =
        new AggregatedValueBinding(this.bindings.count(), aggregationContext, f);
    this.bindings.add(newBinding);
    return newBinding;
  }

  public BindingUnion createBindingUnion(final List<Binding> combinedBindings) {
    DataSequenceClass unionRunClass =
        this.unionRunClassRefinements(combinedBindings);
    BindingUnion newBinding =
        new BindingUnion(this.bindings.count(), unionRunClass, combinedBindings);
    this.bindings.add(newBinding);
    return newBinding;
  }

  public Binding createKeywordBinding(final String token) {
    Binding newBinding =
        new PositionBinding(this.bindings.count(), token, null);
    this.bindings.add(newBinding);
    return newBinding;
  }

  public MapFunctionValueBinding
      createMapFunctionBinding(final String functionName,
                               final List<? extends Binding> inputBindings) {
    MapFunctionValueBinding newBinding =
        new MapFunctionValueBinding(this.bindings.count(), functionName,
                                    inputBindings);
    this.bindings.add(newBinding);
    return newBinding;
  }

  public Binding createNamedKeywordBinding(final String token, final String name) {
    Binding newBinding =
        new PositionBinding(this.bindings.count(), token, name);
    this.bindings.add(newBinding);
    return newBinding;
  }

  public Binding
      createPositionAttributeBinding(final AttributeClass attrClass,
                                     final PositionBinding attachedBinding) {
    Binding newBinding =
        new PositionAttributeBinding(this.bindings.count(), attrClass,
                                     attachedBinding);
    this.bindings.add(newBinding);
    return newBinding;
  }

  public Binding createPositionBinding() {
    Binding newBinding =
        new PositionBinding(this.bindings.count(), null, null);
    this.bindings.add(newBinding);
    return newBinding;
  }

  public BindingProduct createProductBinding(final List<Binding> combinedBindings) {
    DataSequenceClass unionRunClass =
        this.unionRunClassRefinements(combinedBindings);
    BindingProduct newBinding =
        new BindingProduct(this.bindings.count(), unionRunClass, combinedBindings);
    this.bindings.add(newBinding);
    return newBinding;
  }

  public Binding createProductRunBinding(final List<Binding> combinedBindings) {
    DataSequenceClass unionRunClass =
        this.unionRunClassRefinements(combinedBindings);
    Binding newBinding =
        new BindingProduct(this.bindings.count(), unionRunClass, combinedBindings);
    this.bindings.add(newBinding);
    return newBinding;
  }

  public Binding createRunAttributeBinding(final AttributeClass attrClass,
                                           final RunBinding attachedBinding) {
    Binding newBinding =
        new RunAttributeBinding(this.bindings.count(), attrClass, attachedBinding);
    this.bindings.add(newBinding);
    return newBinding;
  }

  public Binding createStructureBinding(final RunClass runClass) {
    Binding newBinding = new RunBinding(this.bindings.count(), runClass);
    this.bindings.add(newBinding);
    return newBinding;
  }

  private DataSequenceClass
      unionRunClassRefinements(final List<Binding> combinedBindings) {
    // Compute a run-class refinement for the new product binding.
    // This will be a *union* of the run-class refinements of the combined
    // bindings.
    List<DataSequenceClass> unionedSequenceClasses =
        new ArrayList<DataSequenceClass>();
    for (Binding binding : combinedBindings) {
      unionedSequenceClasses.add(binding.boundSequenceClass());
    }
    return unionedSequenceClasses.get(0).makeUnion(unionedSequenceClasses);
  }
}